﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace libmorph
{
    public class GrayscaleImage
    {
        /*
         * Image property
         */
        public float this[int x, int y]
        {
            get
            { return this.m_imageData[x, y]; }
            set
            { this.m_imageData[x, y] = value; }
        }

        /*
         * Width property
         */
        public int Width
        {
            get
            { return this.m_imageData.GetLength(0); }
        }

        /*
         * Height property
         */
        public int Height
        {
            get
            { return this.m_imageData.GetLength(1); }
        }

        /*
         * Default constructor (internal only)
         */
        protected GrayscaleImage()
        {
            this.m_imageData = null;
        }

        /*
         * Constructor with dimensions
         */
        public GrayscaleImage(int width, int height)
        {
            this.m_imageData = new float[width, height];
        }

        /*
         * Create from image
         */
        public static GrayscaleImage CreateFromImage(System.Drawing.Bitmap sourceImage)
        {
            GrayscaleImage image = new GrayscaleImage(sourceImage.Width, sourceImage.Height);

            for (int y = 0; y < sourceImage.Height; y++)
            {
                for (int x = 0; x < sourceImage.Width; x++)
                {
                    image.m_imageData[x, y] = sourceImage.GetPixel(x, y).GetBrightness();
                }
            }

            return image;
        }

        /*
         * Convert to bitmap
         */
        public System.Drawing.Bitmap toBitmap()
        {
            System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(this.Width, this.Height);

            for (int y = 0; y < this.Height; y++)
            {
                for (int x = 0; x < this.Width; x++)
                {
                    int pixelValue = (int)(this.m_imageData[x, y] * 255.0f);
                    bitmap.SetPixel(x, y, System.Drawing.Color.FromArgb(pixelValue, pixelValue, pixelValue));
                }
            }

            return bitmap;
        }

        /*
         * Threshold image
         */
        public GrayscaleImage Threshold(float threshold)
        {
            GrayscaleImage returnImage = new GrayscaleImage(this.Width, this.Height);

            for (int y = 0; y < this.Height; y++)
            {
                for (int x = 0; x < this.Width; x++)
                {
                    returnImage[x, y] = (this[x, y] > threshold ? 1.0f : 0.0f);
                }
            }

            return returnImage;
        }

        /*
         * Blur image
         */
        public GrayscaleImage Blur(Mask blurMask)
        {
            GrayscaleImage image = new GrayscaleImage(this.Width, this.Height);

            // For each pixel of image
            for (int imageY = 0; imageY < this.Height; imageY++)
            {
                for (int imageX = 0; imageX < this.Width; imageX++)
                {
                    float sumWeights = 0.0f;
                    float pixelValue = 0.0f;

                    // For each pixel of mask
                    for (int maskY = 0; maskY < blurMask.Height; maskY++)
                    {
                        for (int maskX = 0; maskX < blurMask.Width; maskX++)
                        {
                            // Calculate indices
                            int indexX = imageX - (maskX - blurMask.Width / 2);
                            int indexY = imageY - (maskY - blurMask.Height / 2);

                            // Check array boundaries
                            if (indexX >= 0 && indexX < this.Width && indexY >= 0 && indexY < this.Height)
                            {
                                pixelValue += this[indexX, indexY] * blurMask[maskX, maskY];
                                sumWeights += blurMask[maskX, maskY];
                            }

                        }
                    }

                    image[imageX, imageY] = pixelValue / sumWeights;
                }
            }

            return image;

        }

        /*
         * Dilation
         */
        public GrayscaleImage Dilation(Mask dilationMask)
        {
            GrayscaleImage image = new GrayscaleImage(this.Width, this.Height);

            // For each pixel of image
            for (int imageY = 0; imageY < this.Height; imageY++)
            {
                for (int imageX = 0; imageX < this.Width; imageX++)
                {
                    float value = 0.0f;

                    // For each pixel of mask
                    for (int maskY = 0; maskY < dilationMask.Height; maskY++)
                    {
                        for (int maskX = 0; maskX < dilationMask.Width; maskX++)
                        {
                            // Calculate indices
                            int indexX = imageX - (maskX - dilationMask.Width / 2);
                            int indexY = imageY - (maskY - dilationMask.Height / 2);

                            // Check array boundaries
                            if (indexX >= 0 && indexX < this.Width && indexY >= 0 && indexY < this.Height)
                            {
                                value = Math.Max(value, this[indexX, indexY] * dilationMask[maskX, maskY]);
                            }
                        }
                    }
                    image[imageX, imageY] = value;
                }
            }
            return image;
        }

        /*
         * Modified dilation
         */
        public GrayscaleImage ModifiedDilation(Mask drod1)
        {
            Mask da1 = new Mask(3, 3);
            Mask da2 = new Mask(3, 3);
            Mask da3 = new Mask(3, 3);
            Mask da4 = new Mask(3, 3);
            Mask d2 = new Mask(3, 3);

            da1[0, 1] = 1.0f;
            da1[1, 0] = 1.0f;

            da2[1, 2] = 1.0f;
            da2[2, 1] = 1.0f;

            da3[0, 1] = 1.0f;
            da3[1, 2] = 1.0f;

            da4[1, 0] = 1.0f;
            da4[2, 1] = 1.0f;

            d2[0, 0] = 1.0f;
            d2[0, 2] = 1.0f;
            d2[2, 0] = 1.0f;
            d2[2, 2] = 1.0f;

            GrayscaleImage imagea1 = this.Dilation(da1) - this;
            GrayscaleImage imagea2 = this.Dilation(da2) - this;
            GrayscaleImage imagea3 = this.Dilation(da3) - this;
            GrayscaleImage imagea4 = this.Dilation(da4) - this;

            GrayscaleImage gBis = Max( (imagea1 - imagea2).Abs(), (imagea3 - imagea4).Abs() );

            GrayscaleImage imageDrod1 = this.Dilation(drod1) - this;
            GrayscaleImage imageD2 = this.Dilation(d2) - this;

            return Min(imageDrod1, Min(imageD2, gBis));
        }

        /*
         * Erosion
         */
        public GrayscaleImage Erosion(Mask erosionMask)
        {
            GrayscaleImage image = new GrayscaleImage(this.Width, this.Height);

            // For each pixel of image
            for (int imageY = 0; imageY < this.Height; imageY++)
            {
                for (int imageX = 0; imageX < this.Width; imageX++)
                {
                    float value = 1.0f;

                    // For each pixel of mask
                    for (int maskY = 0; maskY < erosionMask.Height; maskY++)
                    {
                        for (int maskX = 0; maskX < erosionMask.Width; maskX++)
                        {
                            // Calculate indices
                            int indexX = imageX - (maskX - erosionMask.Width / 2);
                            int indexY = imageY - (maskY - erosionMask.Height / 2);

                            // Check array boundaries
                            if (indexX >= 0 && indexX < this.Width && indexY >= 0 && indexY < this.Height)
                            {
                                value = Math.Min(value, this[indexX, indexY] * erosionMask[maskX, maskY]);
                            }
                        }
                    }
                    image[imageX, imageY] = value;
                }
            }
            return image;
        }

         /*
         * Modified erode
         */
        public GrayscaleImage ModifiedErode(Mask drod1)
        {
            Mask da1 = new Mask(3, 3);
            Mask da2 = new Mask(3, 3);
            Mask da3 = new Mask(3, 3);
            Mask da4 = new Mask(3, 3);
            Mask d2 = new Mask(3, 3);

            da1[0, 1] = 1.0f;
            da1[1, 0] = 1.0f;

            da2[2, 1] = 1.0f;
            da2[1, 2] = 1.0f;

            da3[0, 1] = 1.0f;
            da3[1, 2] = 1.0f;

            da4[1, 0] = 1.0f;
            da4[2, 1] = 1.0f;

            d2[0, 0] = 1.0f;
            d2[0, 2] = 1.0f;
            d2[2, 0] = 1.0f;
            d2[2, 2] = 1.0f;

            GrayscaleImage imagea1 = this - this.Erosion(da1);
            GrayscaleImage imagea2 = this - this.Erosion(da2);
            GrayscaleImage imagea3 = this - this.Erosion(da3);
            GrayscaleImage imagea4 = this - this.Erosion(da4);

            GrayscaleImage gBis = Max( (imagea1 - imagea2).Abs(), (imagea3 - imagea4).Abs() );

            GrayscaleImage imageDrod1 = this - this.Erosion(drod1);
            GrayscaleImage imageD2 = this - this.Erosion(d2);

            return Min(imageDrod1, Min(imageD2, gBis));
        }
        
        /*
         * Addition operator
         */
        public static GrayscaleImage operator +(GrayscaleImage component1, GrayscaleImage component2)
        {
            // Check dimension equality
            if (component1.Width != component2.Width || component1.Height != component2.Height)
                return null;

            GrayscaleImage returnValue = new GrayscaleImage(component1.Width, component2.Height);

            for (int y = 0; y < component1.Height; y++)
            {
                for (int x = 0; x < component1.Width; x++)
                {
                    returnValue[x, y] = component1[x, y] + component2[x, y];
                }
            }

            return returnValue;
        }

        /*
         * Subtraction operator
         */
        public static GrayscaleImage operator -(GrayscaleImage component1, GrayscaleImage component2)
        {
            // Check dimension equality
            if (component1.Width != component2.Width || component1.Height != component2.Height)
                return null;

            GrayscaleImage returnValue = new GrayscaleImage(component1.Width, component2.Height);

            for (int y = 0; y < component1.Height; y++)
            {
                for (int x = 0; x < component1.Width; x++)
                {
                    returnValue[x, y] = component1[x, y] - component2[x, y];
                }
            }

            return returnValue;
        }

        /*
         * Multiply
         */
        public static GrayscaleImage operator *(GrayscaleImage image, float multiplier)
        {
            GrayscaleImage returnValue = new GrayscaleImage(image.Width, image.Height);

            for (int y = 0; y < image.Height; y++)
            {
                for (int x = 0; x < image.Width; x++)
                {
                    returnValue[x, y] = image[x, y] * multiplier;
                }
            }

            return returnValue;
        }

        /*
         * Maximum
         */
        public static GrayscaleImage Max(GrayscaleImage image1, GrayscaleImage image2)
        {
            // Check dimension equality
            if (image1.Width != image2.Width || image1.Height != image2.Height)
                return null;

            GrayscaleImage returnValue = new GrayscaleImage(image1.Width, image2.Height);

            for (int y = 0; y < image1.Height; y++)
            {
                for (int x = 0; x < image1.Width; x++)
                {
                    returnValue[x, y] = Math.Max(image1[x, y], image2[x, y]);
                }
            }

            return returnValue;
        }

        /*
         * Minimum
         */
        public static GrayscaleImage Min(GrayscaleImage image1, GrayscaleImage image2)
        {
            // Check dimension equality
            if (image1.Width != image2.Width || image1.Height != image2.Height)
                return null;

            GrayscaleImage returnValue = new GrayscaleImage(image1.Width, image2.Height);

            for (int y = 0; y < image1.Height; y++)
            {
                for (int x = 0; x < image1.Width; x++)
                {
                    returnValue[x, y] = Math.Min(image1[x, y], image2[x, y]);
                }
            }

            return returnValue;
        }

        /*
         * Clamp
         */
        public GrayscaleImage Clamp()
        {
            GrayscaleImage returnValue = new GrayscaleImage(this.Width, this.Height);

            for (int y = 0; y < this.Height; y++)
            {
                for (int x = 0; x < this.Width; x++)
                {
                    returnValue[x, y] = this[x, y] < 0.0f ? 0.0f : (this[x, y] > 1.0f ? 1.0f : this[x, y]);
                }
            }

            return returnValue;
        }

        /*
         * Absolute
         */
        public GrayscaleImage Abs()
        {
            GrayscaleImage returnValue = new GrayscaleImage(this.Width, this.Height);

            for (int y = 0; y < this.Height; y++)
            {
                for (int x = 0; x < this.Width; x++)
                {
                    returnValue[x, y] = Math.Abs(this[x, y]);
                }
            }

            return returnValue;
        }

        /*
         * Main image data
         */
        protected float[,] m_imageData;
    }


}
